package com.ctc;

import com.ctc.serialport.SerialPort;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 设备基类
 */
abstract class CTCDevice
{
    /**
     * 主设备版本号
     */
    public static final int CTC_MASTER_VERSION = 1;
    /**
     * 从设备版本号
     */
    public static final int CTC_SLAVE_VERSION = 1;
    /**
     * 发送事务ID，从1开始，每次发送自增
     */
    protected long sendTransactionId = 1;
    /**
     * 接收事务ID，从0开始，用于防止重复处理
     */
    protected long receiveTransactionId = 0;
    /**
     * 重连次数
     */
    protected int reconnectNumber;
    /**
     * 总接收字节数统计
     */
    protected long totalReceivedBytes;
    /**
     * 总发送字节数统计
     */
    protected long totalSendBytes;
    /**
     * 总发送正确消息数统计
     */
    protected long totalSendMsgCount;
    /**
     * 总发送错误消息数统计
     */
    protected long totalSendErrorMsgCount;
    /**
     * 总接收正确消息数统计
     */
    protected long totalReceivedMsgCount;
    /**
     * 总接收错误消息数统计
     */
    protected long totalReceivedErrorMsgCount;

    /**
     * 主控产品ID，用于标识当前设备
     */
    protected long masterProductId;
    /**
     * 从产品备ID，用于标识连接的从设备
     */
    protected long slaveProductId;
    /**
     * 主控设备ID，用于标识当前设备
     */
    protected byte[] masterDeviceId = new byte[16];
    /**
     * 从设备ID，用于标识连接的从设备
     */
    protected byte[] slaveDeviceId = new byte[16];
    /**
     * 主设备版本
     */
    protected int masterCTCVersion;
    /**
     * 从设备版本
     */
    protected int slaveCTCVersion;
    /**
     * 连接状态
     */
    protected CTCClientConnState deviceConnState = CTCClientConnState.Offline;

    /**
     * 是否已经握手
     */
    protected boolean handshake;

    /**
     * 绑定的串口连接
     */
    protected SerialPort bindSerialPort;
    /**
     * 数据管理器
     */
    protected DataManager dataManager;
    /**
     * 串口是否连接
     */
    public boolean serialConnected;

    /**
     * 接收缓冲区
     */
    protected byte[] bufferData = new byte[1024];
    /**
     * 接收缓冲区长度
     */
    protected int bufferLength = 0;
    /**
     * 最后接收数据的时间（毫秒）
     */
    protected long lastReceiveTime;
    /**
     * 最后发送数据的时间（毫秒）
     */
    private long lastSendTime;
    /**
     * 最后检查连接时间（毫秒）
     */
    private long lastCheckConnectTime;
    /**
     * 重发次数
     */
    private int reNumber = 0;
    /**
     * 最大重发次数
     */
    private int maxReNumber = 3;
    /**
     * 心跳间隔(毫秒)
     */
    private int hearBeratTime = 10000;
    /**
     * 离线时间(毫秒)
     */
    private int offlineTime = 25000;
    /**
     * 重发间隔
     */
    private int resendTime = 400;
    /// <summary>
    /// 发送的包
    /// </summary>
    private DataPacket sendDataPacket = null;

    /**
     * 主设备状态
     */
    protected byte masterState;

    /**
     * 从设备状态
     */
    protected byte slaveState;

    /**
     * 从设备错误码
     */
    protected int slaveErrorCode;
    /**
     * P位
     */
    protected byte player = 0;

    /**
     * 串口输入流
     */
    protected InputStream inputStream = null;
    /**
     * 串口输出流
     */
    protected OutputStream outputStream = null;

    /**
     * 线程池
     */
    protected ExecutorService executorService;
    /**
     * 串口名称
     */
    protected String serialName = "";

    /**
     * 波特率
     */
    protected int baudRate = 0;

    /**
     * 事件监听
     */
    protected CTCEvent.IDevice listener;


    /**
     * 获取连接状态
     */
    public CTCClientConnState getConnState()
    {
        return deviceConnState;
    }

    /**
     * 获取离线时间
     *
     * @return 离线时间（毫秒）
     */
    public long getOfflineTime()
    {
        return System.currentTimeMillis() - lastReceiveTime;
    }

    /**
     * 获取重试次数
     *
     * @return 重试次数
     */
    public int getReConnectTimes()
    {
        return reconnectNumber;
    }

    /**
     * 获取总接收字节数
     *
     * @return 总接收字节数
     */
    public long getTotalReceivedBytes()
    {
        return totalReceivedBytes;
    }

    /**
     * 获取总发送字节数
     *
     * @return 总发送字节数
     */
    public long getTotalSendBytes()
    {
        return totalSendBytes;
    }

    /**
     * 获取总发送消息数
     *
     * @return 总发送消息数
     */
    public long getTotalSendMsgCount()
    {
        return totalSendMsgCount;
    }

    /**
     * 获取总发送错误消息数
     *
     * @return 总发送错误消息数
     */
    public long getTotalSendErrorMsgCount()
    {
        return totalSendErrorMsgCount;
    }

    /**
     * 获取总接收消息数
     *
     * @return 总接收消息数
     */
    public long getTotalReceivedMsgCount()
    {
        return totalReceivedMsgCount;
    }

    /**
     * 获取总接收错误消息数
     *
     * @return 总接收错误消息数
     */
    public long getTotalReceivedErrorMsgCount()
    {
        return totalReceivedErrorMsgCount;
    }

    /**
     * 获取主控产品ID
     *
     * @return 主控产品ID
     */
    public long getMasterProductId()
    {
        return masterProductId;
    }

    /**
     * 获取从产品备ID
     *
     * @return 从产品备ID
     */
    public long getSlaveProductId()
    {
        return slaveProductId;
    }

    /**
     * 获取主控设备ID
     *
     * @return 主控设备ID
     */
    public byte[] getMasterDeviceId()
    {
        return masterDeviceId;
    }

    /**
     * 获取从设备ID
     *
     * @return 从设备ID
     */
    public byte[] getSlaveDeviceId()
    {
        return slaveDeviceId;
    }

    /**
     * 获取主控CTC版本
     *
     * @return 主控CTC版本
     */
    public int getMasterCTCVersion()
    {
        return masterCTCVersion;
    }

    /**
     * 获取从设备CTC版本
     *
     * @return 从设备CTC版本
     */
    public int getSlaveCTCVersion()
    {
        return slaveCTCVersion;
    }

    /**
     * 处理接收数据
     *
     * @param packet 收到的数据包
     */
    protected abstract void handleReceive(DataPacket packet);


    /**
     * 主设备发送心跳,从设备只回应
     */
    protected void sendHeartbeat()
    {
    }

    /**
     * 构造函数
     *
     * @param player     P位
     * @param serialName 串口名称
     * @param baudRate   波特率
     */
    public CTCDevice(byte player, String serialName, int baudRate)
    {
        this.player = player;
        this.serialName = serialName;
        this.baudRate = baudRate;
        dataManager = new DataManager();
        executorService = Executors.newFixedThreadPool(2);
    }

    public void open()
    {
        if (!serialConnected)
        {
            try
            {
                if (bindSerialPort != null)
                {
                    bindSerialPort.closeSerialPort();
                }
                bindSerialPort = new SerialPort();
                bindSerialPort.openSerialPort(new File(serialName), baudRate);
                inputStream = bindSerialPort.getInputStream();
                outputStream = bindSerialPort.getOutputStream();
                serialConnected = true;
                handshake = false;
                deviceConnState = CTCClientConnState.Connecting;
                lastCheckConnectTime = System.currentTimeMillis();
                lastSendTime = System.currentTimeMillis();
                lastReceiveTime = System.currentTimeMillis();
                handleOnSerialOpen(null);
                startThread();
            }
            catch (Exception e)
            {
                serialConnected = false;
                logE("打开串口失败: " + e.getMessage());
                handleOnSerialOpen(e.getMessage());
            }
        }

    }

    /**
     * 关闭串口
     */
    public void close()
    {
        if (bindSerialPort != null)
        {
            serialConnected = false;
            bindSerialPort.closeSerialPort();
            bindSerialPort = null;

        }
    }

    /**
     * 发送数据
     *
     * @param packet 数据包
     */
    protected void send(DataPacket packet)
    {
        dataManager.addData(packet);
    }

    /**
     * 发送ACK
     *
     * @param packet 数据包
     */
    protected void sendACK(DataPacket packet)
    {
        if (handshake || checkInst(CTCMsg.CTC_CMD_HANDSHAKE, new byte[]{packet.getData()[0], packet.getData()[1]}) || checkInst(CTCMsg.CTC_CMD_HANDSHAKE_RSP, new byte[]{packet.getData()[0], packet.getData()[1]}))
        {
            dataManager.addACKData(packet);
        }
    }

    /**
     * 真正的发送数据
     *
     * @param packet 数据包
     */
    private void realSend(DataPacket packet) throws IOException
    {
        if (packet == null)
        {
            return;
        }
        byte[] sendData = packet.toArray();
        outputStream.write(sendData, 0, sendData.length);
        outputStream.flush();
        logI(String.format("发送--%s", CRC.toHexString(sendData)));
        lastSendTime = System.currentTimeMillis();
        totalSendBytes += sendData.length;

    }


    /**
     * 开始处理数据
     */
    protected void startThread()
    {
        executorService.submit(this::receiveThread);
        executorService.submit(this::sendThread);
    }


    /**
     * 发送数据线程
     */
    private void sendThread()
    {
        while (serialConnected)
        {
            try
            {
                sleep(10);
                synchronized (this)
                {
                    //如果有ACK要回应就直接发
                    if (dataManager.hasAckData())
                    {
                        realSend(dataManager.getAckData());
                        continue;
                    }
                    if (sendDataPacket == null)
                    {//如果没有正在发送的包就取新的
                        if (!dataManager.hasData())
                        {
                            continue;
                        }
                        sendDataPacket = dataManager.getData();
                        realSend(sendDataPacket);
                        reNumber = 1;
                    }
                    else
                    {//如果有正在发送的包检查是不是超时了
                        if (System.currentTimeMillis() - lastSendTime > resendTime)
                        {
                            if (reNumber < maxReNumber)
                            {
                                realSend(sendDataPacket);
                                reNumber++;
                                logI("重发数据");
                            }
                            else
                            {
                                handleOnSendFail(CTCMsg.CTC_ACK_TIMEOUT, sendDataPacket);
                                logE("重发数据失败");
                                sendDataPacket = null;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                close();
                handleOnSerialClose("串口已关闭-写入数据出错:" + e.toString());
                return;
            }
        }
    }

    /**
     * 接收数据处理
     */
    private void receiveThread()
    {
        byte[] buffer = new byte[256];
        int readLength = 0;
        while (serialConnected && inputStream != null)
        {
            try
            {
                sleep(10);
                if (inputStream.available() > 0)
                {
                    readLength = inputStream.read(buffer);
                    totalReceivedBytes += readLength;
                    if (bufferLength + readLength > bufferData.length)
                    {
                        bufferLength = 0;
                    }
                    System.arraycopy(buffer, 0, bufferData, bufferLength, readLength);
                    bufferLength += readLength;
                    while (isDataLegal()) ;
                }
                else
                {
                    //如果是主设备，检查是否发心跳
                    if (System.currentTimeMillis() - lastCheckConnectTime > hearBeratTime && sendDataPacket == null && dataManager.isEmpty())
                    {
                        lastCheckConnectTime = System.currentTimeMillis();
                        handleTimeOut();
                    }//判断是否掉线
                    else if (System.currentTimeMillis() - lastReceiveTime > offlineTime && deviceConnState == CTCClientConnState.Connected)
                    {
                        handleOffline();
                    }
                }

            }
            catch (Exception e)
            {
                close();
                handleOnSerialClose("串口已关闭-读取数据出错:" + e.toString());
                break;
            }
        }
    }

    /**
     * 检查数据是否合法
     */
    protected boolean isDataLegal()
    {
        //检查是否达到最小包长度
        if (bufferLength < DataPacket.PACKET_LENGTH)
        {
            return false;
        }
        //检查包头是否正确，出错就重新寻找包头
        if (bufferData[0] != DataPacket.DATA_HEAD[0] || bufferData[1] != DataPacket.DATA_HEAD[1])
        {
            logE("数据包头错误,重新寻找包头");
            for (int i = 2; i < bufferLength - 1; i++)
            {
                if (bufferData[i] == DataPacket.DATA_HEAD[0] && bufferData[i + 1] == DataPacket.DATA_HEAD[1])
                {
                    logI("找到数据包头了");
                    bufferLength -= i;
                    System.arraycopy(bufferData, i, bufferData, 0, bufferLength);
                    return true;
                }
                if (i == bufferLength - 2)
                {
                    bufferLength = 0;
                    logE("没有找到数据包头,清空缓存");
                    return false;
                }
            }
        }

        // 读取P位
        byte player = bufferData[2];
        // 读取命令标识
        byte[] inst = new byte[]{bufferData[3], bufferData[4]};
        // 读取事务ID
        byte[] packetID = new byte[]{bufferData[5], bufferData[6], bufferData[7], bufferData[8]};
        // 读取长度
        int dateLen = bufferData[9] & 0xFF;
        int totalData = DataPacket.PACKET_LENGTH + dateLen;
        if (bufferLength < totalData)
        {
            return false;
        }
        //检验数据包CRC
        long crc0 = CRC.crc32(bufferData, 0, 10 + dateLen);
        long crc1 = CRC.bytesToUint32(bufferData[10 + dateLen], bufferData[10 + dateLen + 1], bufferData[10 + dateLen + 2], bufferData[10 + dateLen + 3]);

        if (crc0 != crc1)
        {
            bufferLength -= 2;
            System.arraycopy(bufferData, 2, bufferData, 0, bufferLength);
            logE("数据包CRC错误，删除指令头，开始下一次判断");
            return true;
        }
        //检查通过，从缓存中取出数据，开始进行数据操作
        lastCheckConnectTime = System.currentTimeMillis();
        lastReceiveTime = System.currentTimeMillis();
        byte[] tempData = new byte[totalData];
        System.arraycopy(bufferData, 0, tempData, 0, totalData);
        bufferLength -= totalData;
        System.arraycopy(bufferData, totalData, bufferData, 0, bufferLength);
        //
        logI(String.format("收到--%s ", CRC.toHexString(tempData)));
        checkACK(DataPacket.format(tempData));
        return true;
    }

    /**
     * 检查指令类型
     *
     * @param dataPacket 收到的数据包
     */
    private void checkACK(DataPacket dataPacket)
    {
        //处理ACK
        if (checkInst(dataPacket.getInst(), CTCMsg.CTC_CMD_ACK))
        {//ACK回应
            if (dataPacket.getDataLength() != 3)
            {
                logE(String.format("CTC ACK Length Error:%s", dataPacket.getDataLength()));
                sendACK(dataPacket, CTCMsg.CTC_ACK_LENGTH_ERR);
                return;
            }
            handleAckResponse(dataPacket);
            return;
        }
        //如果事务ID为1就重新开始
        if (dataPacket.getTransactionId() == 1)
        {
            receiveTransactionId = 0;
        }
        if (dataPacket.getTransactionId() == receiveTransactionId)
        {
            // 重复的事务ID,丢弃此帧数据,但回应ACK
            logE(String.format("CTC Receive Buffer Duplicate TransactionID:%s", dataPacket.getTransactionId()));
            sendACK(dataPacket, CTCMsg.CTC_ACK_SUCCESS);
            return;
        }
        //////////////////////////////
        receiveTransactionId = dataPacket.getTransactionId();
        handleReceive(dataPacket);
    }


    /**
     * 检查指令是否相同
     *
     * @param inst0 指令0
     * @param inst1 指令1
     * @return 是否相同
     */
    protected boolean checkInst(byte[] inst0, byte[] inst1)
    {
        if (inst0 == null || inst1 == null)
        {
            return false;
        }
        if (inst0.length != 2 || inst1.length != 2)
        {
            return false;
        }
        if (inst0[0] != inst1[0])
        {
            return false;
        }
        if (inst0[1] != inst1[1])
        {
            return false;
        }
        return true;
    }

    /**
     * 处理ACK回应
     *
     * @param dataPacket 收到的数据包
     */
    protected void handleAckResponse(DataPacket dataPacket)
    {
        if (dataPacket.getData()[2] == CTCMsg.CTC_ACK_SUCCESS)
        {
            handleOnSendSuccess(sendDataPacket);
        }
        else
        {
            handleOnSendFail(dataPacket.getData()[2], sendDataPacket);
        }
        sendDataPacket = null;
    }

    /**
     * 回应ACK
     *
     * @param dataPacket 收到的数据包
     * @param result     ACK结果码
     */
    protected void sendACK(DataPacket dataPacket, byte result)
    {
        if (result == CTCMsg.CTC_ACK_SUCCESS)
        {
            totalReceivedMsgCount++;
        }
        else
        {
            totalReceivedErrorMsgCount++;
        }
        DataPacket sendData = new DataPacket(dataPacket.getPlayer(), CTCMsg.CTC_CMD_ACK, dataPacket.getTransactionId(), dataPacket.getInst()[0], dataPacket.getInst()[1], result);
        sendACK(sendData);
    }

    /**
     * 处理超时
     */
    protected void handleTimeOut()
    {
    }

    /**
     * 处理离线
     */
    protected void handleOffline()
    {
        handshake = false;
        handleOnConnectStateChange(CTCClientConnState.Connecting);
    }

    /**
     * 处理连接状态变化
     *
     * @param state 连接状态
     */
    protected void handleOnConnectStateChange(CTCClientConnState state)
    {
        if (deviceConnState != state)
        {
            deviceConnState = state;
            if (listener != null)
            {
                listener.onConnectStateChange(player, state);
            }
        }
    }

    /**
     * 处理发送失败
     *
     * @param ackState   ACK结果码(CTC_CMD_ACK)
     * @param dataPacket 数据包
     */
    protected void handleOnSendFail(byte ackState, DataPacket dataPacket)
    {
        totalSendErrorMsgCount++;
        switch (ackState)
        {
            case CTCMsg.CTC_ACK_UNSUPPORTED:
                logE("ACK-不支持指令");
                break;
            case CTCMsg.CTC_ACK_LENGTH_ERR:
                logE("ACK-长度错误");
                break;
            case CTCMsg.CTC_ACK_BUSY:
                logE("ACK-设备忙");
                break;
            case CTCMsg.CTC_ACK_HEADER_ERR:
                logE("ACK-包头错误");
                break;
            case CTCMsg.CTC_ACK_CRC_ERR:
                logE("ACK-CRC错误");
                break;
            default:
                // 未知ACK应答,不需要处理
                break;
        }
        if (listener != null)
        {
            listener.onSendFail(ackState, player, dataPacket.getInst(), dataPacket.getData());
        }
    }

    /**
     * 处理发送成功
     *
     * @param dataPacket 数据包
     */
    protected void handleOnSendSuccess(DataPacket dataPacket)
    {
        totalSendMsgCount++;
        if (listener != null)
        {
            listener.onSendSuccess(player, dataPacket.getInst(), dataPacket.getData());
        }
    }


    /**
     * 处理串口打开事件
     *
     * @param msg 成功返回null，失败返回错误信息
     */
    protected void handleOnSerialOpen(String msg)
    {
        if (listener != null)
        {
            listener.onSerialOpen(player, msg);
        }
    }

    /**
     * 处理串口关闭事件
     *
     * @param msg 成功返回null，失败返回错误信息
     */
    protected void handleOnSerialClose(String msg)
    {
        if (listener != null)
        {
            listener.onSerialClose(player, msg);
        }
    }


    /**
     * 记录日志
     */
    protected void logI(String message)
    {
        if (listener != null)
        {
            listener.debugLogI(player, message);
        }
    }


    /**
     * 记录错误日志
     */
    protected void logE(String message)
    {
        if (listener != null)
        {
            listener.debugLogE(player, message);
        }
    }

    /**
     * 线程睡眠
     *
     * @param ms 毫秒
     */
    protected void sleep(int ms)
    {
        try
        {
            Thread.sleep(ms);
        }
        catch (InterruptedException e)
        {
            logE("线程睡眠失败: " + e.getMessage());
        }
    }


}